using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

// classes defining SCDL DOM
namespace SCDL {

    // Attrbibute with value of auto-recognized type
    public class ScdlAttribute {
        string _name;
        string _value;
        bool _isRequired;
        bool _isKey;
        Type _valueType;
        string _stringValue;
        bool _boolValue;
        TimeSpan _timeSpanValue;
        int _intValue;

        public ScdlAttribute(string name, string value) {
            _name = name;
            _value = value;
            ParseValue();
        }

        public ScdlAttribute(string name, string value, bool isRequired, bool isKey) 
            : this(name, value) {
            _isRequired = isRequired;
            _isKey = isKey;
        }

        public string   Name        { get { return _name; } }
        public string   Value       { get { return _value; } }
        public bool     IsRequired  { get { return _isRequired; } }
        public bool     IsKey       { get { return _isKey; } }
        public Type     ValueType   { get { return _valueType; } }

        public string   StringValue     { get { ValidateValueType(typeof(string));      return _stringValue; } }
        public bool     BoolValue       { get { ValidateValueType(typeof(bool));        return _boolValue; } }
        public TimeSpan TimeSpanValue   { get { ValidateValueType(typeof(TimeSpan));    return _timeSpanValue; } }
        public int      IntValue        { get { ValidateValueType(typeof(int));         return _intValue; } }

        void ParseValue() {
            if (string.IsNullOrEmpty(_value)) {
                _valueType = typeof(string);
                _stringValue = String.Empty;
            }
            else if (bool.TryParse(_value, out _boolValue)) {
                _valueType = typeof(bool);
            }
            else if (_value.IndexOf(':') > 0 && TimeSpan.TryParse(_value, out _timeSpanValue)) {
                _valueType = typeof(TimeSpan);
            }
            else if (int.TryParse(_value, out _intValue)) {
                _valueType = typeof(int);
            }
            else {
                _valueType = typeof(string);
                _stringValue = _value;
            }
        }

        void ValidateValueType(Type expectedType) {
            if (_valueType != expectedType) {
                throw new InvalidOperationException(
                    string.Format("Type of the attribute value {0} doesn't match the expected type {1}.", 
                        _valueType.Name, expectedType.Name));
            }
        }
    }

    // base class for the nodes - name and list of attributes
    public class ScdlNode {
        string _name;
        List<ScdlAttribute> _attributes;

        public ScdlNode(string name) {
            _name = name;
            _attributes = new List<ScdlAttribute>();
        }

        public string Name { get { return _name; } }
        public List<ScdlAttribute> Attributes { get { return _attributes; } }
    }

    // sub-collection - has item attributes
    public class ScdlCollection : ScdlNode {
        List<ScdlAttribute> _itemAttributes;

        public ScdlCollection(string name) : base(name) {
            _itemAttributes = new List<ScdlAttribute>();
        }

        public List<ScdlAttribute> ItemAttributes { get { return _itemAttributes; } }
    }

    // section - node with sub-elements and sub-collections
    public class ScdlSection : ScdlNode {
        List<ScdlNode> _subNodes;
        List<ScdlCollection> _subCollections;

        public ScdlSection(string name) : base(name) {
            _subNodes = new List<ScdlNode>();
            _subCollections = new List<ScdlCollection>();
        }

        public List<ScdlNode> SubNodes { get { return _subNodes; } }
        public List<ScdlCollection> SubCollections { get { return _subCollections; } }
    }

    // document - namespace and list of sections
    public class ScdlDocument {
        string _namespace;
        List<ScdlSection> _sections;

        public ScdlDocument() {
            _namespace = string.Empty;
            _sections = new List<ScdlSection>();
        }

        public string Namespace { get { return _namespace; } }
        public List<ScdlSection> Sections { get { return _sections; } }

        // parse XML into SCDL
        public void LoadFromXml(XmlDocument doc) {
            // get the root element node
            XmlNode rootNode = doc.DocumentElement;
            _namespace = rootNode.Name;

            // for each node generate config section
            for (XmlNode node = rootNode.FirstChild; node != null; node = node.NextSibling) {
                if (node.NodeType == XmlNodeType.Element) {
                    _sections.Add(LoadSectionFromXml(node));
                }
            }
        }

        // parse XML fragment into an SCDL section
        static ScdlSection LoadSectionFromXml(XmlNode sectionNode) {
            ScdlSection section = new ScdlSection(sectionNode.Name);

            // add attributes
            foreach (XmlAttribute attr in sectionNode.Attributes) {
                section.Attributes.Add(new ScdlAttribute(attr.Name, attr.Value));
            }

            // process sub-collections and sub-elements
            for (XmlNode node = sectionNode.FirstChild; node != null; node = node.NextSibling) {
                if (node.NodeType != XmlNodeType.Element) {
                    continue;
                }

                if (node.Name == "add" || node.Name == "remove" || node.Name == "clear") {
                    throw new NotSupportedException(String.Format(
                        "Default collections (in section '{0}') are not supported.",
                        sectionNode.Name));
                }

                if (node.FirstChild == null) {
                    section.SubNodes.Add(LoadSubNodeFromXml(node));
                }
                else if (node.FirstChild.Name == "add") {
                    section.SubCollections.Add(LoadSubCollectionFromXml(node, node.FirstChild));
                }
                else {
                    throw new NotSupportedException(String.Format(
                        "Nested non-collections sub-elements (in section '{0}' element '{1}') are not supported.",
                        sectionNode.Name, node.Name));
                }
            }

            return section;
        }

        // parse XML fragment into an SCDL node
        static ScdlNode LoadSubNodeFromXml(XmlNode elementNode) {
            ScdlNode node = new ScdlNode(elementNode.Name);

            // add attributes
            foreach (XmlAttribute attr in elementNode.Attributes) {
                node.Attributes.Add(new ScdlAttribute(attr.Name, attr.Value));
            }

            return node;
        }

        // parse XML fragment into an SCDL collection
        static ScdlCollection LoadSubCollectionFromXml(XmlNode collectionNode, XmlNode addElementNode) {
            ScdlCollection collection = new ScdlCollection(collectionNode.Name);

            // add attributes from the collection tag
            foreach (XmlAttribute attr in collectionNode.Attributes) {
                collection.Attributes.Add(new ScdlAttribute(attr.Name, attr.Value));
            }

            // add attributes from the item tag
            bool firstAttribute = true;
            foreach (XmlAttribute attr in addElementNode.Attributes) {
                collection.ItemAttributes.Add(new ScdlAttribute(attr.Name, attr.Value, firstAttribute, firstAttribute));
                firstAttribute = false;
            }

            return collection;
        }
    }
}
